package com.xm.web.config;

import cn.hutool.core.collection.CollUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xm.web.utils.ClassUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*;

/**
 * Cacheable 扩展(默认不支持自定义过期时间)
 * <p>
 * example: 添加缓存 @Cacheable(value = "userSelectByPageCache", key = "'userSelectByPageCache#600#' + #userRequest.pageNum + '_'+ #userRequest.pageSize + '_' +#userRequest.nickname")
 * value: 分组
 * key: redis键, 支持表达式拼接请求参数, 这里截取key中按定义规则的过期时间(秒级)
 * return的结果为redis缓存的值
 * example: 更新/删除 @CacheEvict(value = "userSelectByPageCache", allEntries = true)
 * value: 等价于@Cacheable的value分组
 * allEntries: true 全部节点
 * 更新/删除直接清除@Cacheable的value分组下所有数据
 */
@Configuration
@EnableCaching
public class RedisConfig {

    @Value("${redis.cache.separator:#}")
    private String separator;
    @Value("${redis.cache.package:com.xm.web.controller}")
    private String packageName;

    // 以下两种redisTemplate自由根据场景选择
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer serializer = serializer();
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        //这里设置redis事务一致
        template.setEnableTransactionSupport(true);
        return template;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        stringRedisTemplate.setEnableTransactionSupport(true);
        return stringRedisTemplate;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(60), // 默认策略，未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Jackson2JsonRedisSerializer serializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值（默认使用JDK的序列化方式）
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.findAndRegisterModules();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        serializer.setObjectMapper(mapper);
        return serializer;
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        validAnnotation(ClassUtil.getClasses(packageName), redisCacheConfigurationMap);
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(serializer())).entryTtl(Duration.ofSeconds(seconds));
    }

    /**
     * 自定义缓存失效时间
     */
    private void validAnnotation(List<Class<?>> clsList, Map<String, RedisCacheConfiguration> redisCacheConfigurationMap) {
        if (CollUtil.isEmpty(clsList)) {
            return;
        }
        clsList.forEach(cls -> {
            Method[] methods = cls.getDeclaredMethods();
            if (Objects.isNull(methods)) {
                return;
            }
            for (Method method : methods) {
                Cacheable cacheable = method.getAnnotation(Cacheable.class);
                if (Objects.isNull(cacheable)) {
                    continue;
                }
                String[] split = cacheable.key().replace("'", "").split(separator);
                if (split.length >= 2) {
                    Arrays.stream(cacheable.value()).forEach(v -> redisCacheConfigurationMap.put(v, this.getRedisCacheConfigurationWithTtl(Integer.valueOf(split[1]))));
                }
            }
        });
    }
}
